作者:Missluckyyy_879 | 来源:互联网 | 2023-05-23 16:45
篇首语:本文由编程笔记#小编为大家整理,主要介绍了c++虚表学习2相关的知识,希望对你有一定的参考价值。 前文 本文会让读者明白虚表原理,理解类的大致内存结构。棱形继承下内存布局&#xf
篇首语:本文由编程笔记#小编为大家整理,主要介绍了c++虚表学习2相关的知识,希望对你有一定的参考价值。
前文
- 本文会让读者明白虚表原理,
- 理解类的大致内存结构。棱形继承下内存布局,和虚继承单一内存布局情况。
- 父类构造函数调用虚函数会怎么样
- 父类析构函数调用虚函数情况
- 为什么析构函数一定要是虚函数
单继承
我们首先参阅如下的代码:
class Person
public:
int pFlag = 2;
Person()
printf("Person \\r\\n");
virtual ~Person()
printf(" ~Person \\r\\n");
virtual void vSayPerson()
printf("Person \\r\\n");
void nSayPerson()
printf("nSayPerson \\r\\n");
;
class XH :public Person
public:
int xhFlag = 4;
virtual ~XH()
printf("~XH \\r\\n");
XH()
printf("XH \\r\\n");
virtual void vSayXH()
printf("vSayXH \\r\\n");
virtual void vSayXH2()
printf("vSayXH2 \\r\\n");
void nSayXH()
printf("nSayXH \\r\\n");
;
int main()
XH* pXh = new XH();
pXh->nSayPerson();
pXh->vSayPerson();
pXh->vSayXH2();
pXh->nSayXH();
delete pXh;
return 0;
Debug编译后的汇编代码
我们跟进到构造函数中:
我们按照上面的顺序逐个分析
- 调用父类的构造函数。因为子类可能会用到父类的东西
- 给自己虚表赋值,注意虚表在内存首地址。
- 给自己变量赋值 。在上面你注意 [eax+8] 这个地址不是eax+4,证明中间还有其他东西。这里存放的是父类的局部变量
- 调用自身构造方法内的函数体
我们首先构建出整个内存图:
构造函数要先调父类的初始化函数:
因为子类会用到父类资源,比如子类获取父类的变量
先初始化虚表指针在调用属性初始化和方法体:
因为构造函数会有可能调用虚函数
先初始化属性在调用方法体:
方法体可能会获取属性
我们首先虚表的赋值代码:
我们接下来看下Person这个类的初始化函数
你会差异的发现父类构造也会填入自己虚表,完成父类构造的后,子类又会覆盖写入这个虚表地址。
这样会有什么关系和异常呢?假设子类重写父类的虚函数,在父类构造函数调用虚函数只会调用自己的函数而不是子类的。
我们看下XH虚表地址的交叉引用信息
我们观察下XH析构函数:
为啥需要在自己析构函数中再次给自己的虚表赋值呢?为了解答这个答案我们首先才看~Person析构
在析构对象流程,首先释放子类的所有子类资源,在释放父类所有资源。因为子类资源被释放了,如果调用到父类时虚表没有还原父类的虚表,那么父类析构中有调用虚函数的可能会引起意外的异常。因为指向的函数是一个释放资源的子类函数。
我们最后看看几个虚函数和非虚函数的调用
pXh->nSayPerson();
pXh->vSayPerson();
首先我们要知道的是XH的虚表中第二项就是vSayPerson函数地址,第一项是析构函数代理函数地址。
现在你应该对虚函数的调用有一定的认识了吧。现在你应该举一反三回答出为啥析构函数为啥一定要是虚函数了。。。
我们现在重写Person虚函数看看XH的虚表会怎么样.
class XH :public Person
public:
int xhFlag = 4;
virtual ~XH()
printf("~XH \\r\\n");
XH()
printf("XH \\r\\n");
virtual void vSayXH()
printf("vSayXH \\r\\n");
virtual void vSayXH2()
printf("vSayXH2 \\r\\n");
void nSayXH()
printf("nSayXH \\r\\n");
virtual void vSayPerson()
printf("XH vSayPerson\\r\\n");
;
多继承
class BaseClass
public:
int baseFlag = -1;
int fill[32] = 0;
BaseClass(int flag)
baseFlag = flag;
printf("BaseClass \\r\\n");
virtual ~BaseClass()
printf(" ~BaseClass \\r\\n");
;
class Person : public BaseClass
public:
int pFlag = 2;
Person() :BaseClass(2)
printf("Person \\r\\n");
virtual ~Person()
printf(" ~Person \\r\\n");
virtual void vSayPerson()
printf("Person \\r\\n");
void nSayPerson()
printf("nSayPerson \\r\\n");
;
class Female : public BaseClass
public:
int fFlag = 3;
Female() :BaseClass(3)
printf("Female \\r\\n");
virtual ~Female()
printf(" ~Female \\r\\n");
virtual void vSayFemale()
printf("vSayFemale \\r\\n");
void nSayFemale()
printf("nSayFemale \\r\\n");
;
class XM :public Person, public Female
public:
virtual void vSayFemale()
printf("vSayFemale \\r\\n");
virtual ~XM()
printf("~XM \\r\\n");
XM()
printf("XM \\r\\n");
virtual void sayXm()
printf("sayXm \\r\\n");
;
int main()
XM* pXm = new XM();
printf("%d \\r\\n", sizeof XM);
delete pXm;
return 0;
我们可以看到多继承下XM输出类大小是280字节,我们改用虚继承
class BaseClass
public:
int baseFlag = -1;
int fill[32] = 0;
BaseClass(int flag)
baseFlag = flag;
printf("BaseClass \\r\\n");
virtual ~BaseClass()
printf(" ~BaseClass \\r\\n");
;
class Person : virtual public BaseClass
public:
int pFlag = 2;
Person() :BaseClass(2)
printf("Person \\r\\n");
virtual ~Person()
printf(" ~Person \\r\\n");
virtual void vSayPerson()
printf("Person \\r\\n");
void nSayPerson()
printf("nSayPerson \\r\\n");
;
class Female :virtual public BaseClass
public:
int fFlag = 3;
Female() :BaseClass(3)
printf("Female \\r\\n");
virtual ~Female()
printf(" ~Female \\r\\n");
virtual void vSayFemale()
printf("vSayFemale \\r\\n");
void nSayFemale()
printf("nSayFemale \\r\\n");
;
class XM :public Person, public Female
public:
virtual void vSayFemale()
printf("vSayFemale \\r\\n");
virtual ~XM()
printf("~XM \\r\\n");
XM():BaseClass(0x123)
printf("XM \\r\\n");
virtual void sayXm()
printf("sayXm \\r\\n");
;
int main()
XM* pXm = new XM();
printf("%d \\r\\n", sizeof XM);
delete pXm;
return 0;
普通多继承类内存视图
可见虚继承减少部分内存,我们首先研究下分非虚继承下的XM内存结构
我们直接查看XM的构造函数
析构方法
多继承类内存视图
我们知道虚继承可减少共同父类占用空间,比如本例中XM类会有两个BaseClass类,因此我们会想280减去一个Base类大小
就是虚继承后的大小。具体数值为 144=280-136
,但是我们通过允许后发现实际内存是160大小。
int main()
XM* pXm = new XM();
printf("sizeof XM %d \\r\\n", sizeof XM);
printf("sizeof Female %d \\r\\n", sizeof Female);
printf("sizeof Person %d \\r\\n", sizeof Person);
printf("sizeof BaseClass %d \\r\\n", sizeof BaseClass);
delete pXm;
return 0;
我们很明显发现Female和Person大小变大了8字节.
为了研究这个问题们修改以下代码首先构造一个Female
int main()
Female* pXm = new Female();
printf("sizeof XM %d \\r\\n", sizeof XM);
printf("sizeof Female %d \\r\\n", sizeof Female);
printf("sizeof Person %d \\r\\n", sizeof Person);
printf("sizeof BaseClass %d \\r\\n", sizeof BaseClass);
delete pXm;
return 0;
我们这里为方便理解直接给出虚继承内存结构:
IDA PRO 查看构造函数你会发现这个代码会有分支结构,有可能不会调用虚基类初始化函数
我们最后再看看XM这个类的内存结构体: